package com.redis.impl;

import com.redis.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * redis可重入自旋锁实现异步续命，防止超时的情况出现，锁被夺走
 * 实现原理：增加一个线程，异步更新该锁的失效时间，直到加锁的线程将锁释放
 *
 * @author : SongRenShuo
 * @date : 2024/08/28
 */
@Slf4j
@Service
public class RedisLockImplAsynTime implements RedisLock {
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    private final ThreadLocal<String> localUid = new ThreadLocal<>();
    private final ThreadLocal<AtomicInteger> localInteger = new ThreadLocal<>();
    private final ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
    private final ThreadLocal<ScheduledFuture<?>> scheduledFuture = new ThreadLocal<>();
    /**
     * 自旋锁的等待时间
     */
    private static final long REENTRY_SPIN_SLEEP = 1000;
    /**
     * 最大自旋时间
     */
    private static final long MAX_SPIN_TIME = 30000;

    @PostConstruct
    public void init() {
        // 设置线程池大小
        taskScheduler.setPoolSize(10);
        taskScheduler.initialize();
    }

    @Override
    public boolean tryLock(String lockKey, long timeout, TimeUnit unit) {
        boolean isLock;
        if (localUid.get() == null) {
            String uuid = UUID.randomUUID().toString();
            localUid.set(uuid);
            long startTime = System.currentTimeMillis();
            while (true) {
                isLock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, timeout, unit));
                if (isLock) {
                    break;
                }
                if (System.currentTimeMillis() - startTime >= MAX_SPIN_TIME) {
                    log.info("lockKey：{} 线程：{} 获取锁超时", lockKey, Thread.currentThread().getName());
                    break;
                }
                try {
                    Thread.sleep(REENTRY_SPIN_SLEEP);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 恢复中断状态
                    log.error("thread sleep interrupted, releasing lock", e);
                    releaseLock(lockKey); // 释放锁
                    return false;
                }
            }
            if (isLock) {
                log.info("lockKey：{} 线程：{} 获取锁成功", lockKey, Thread.currentThread().getName());
                localInteger.set(new AtomicInteger(0));
                scheduleAddTimeForLock(lockKey, uuid, timeout, unit);
            }
        } else {
            log.info("lockKey：{} 线程：{} 获取重入锁成功", lockKey, Thread.currentThread().getName());
            isLock = true;
            // 如果已经上锁，则设置重入次数加一
            localInteger.get().incrementAndGet();
        }

        return isLock;
    }

    @Override
    public void releaseLock(String lockKey) {
        String uuid = localUid.get();
        if (uuid != null && uuid.equalsIgnoreCase(redisTemplate.opsForValue().get(lockKey))) {
            AtomicInteger count = localInteger.get();
            if (count != null) {
                if (count.decrementAndGet() > 0) {
                    log.info("lockKey：{} 线程：{} 重入锁释放", lockKey, Thread.currentThread().getName());
                } else {
                    log.info("lockKey：{} 线程：{} 释放锁", lockKey, Thread.currentThread().getName());
                    cancelAddTimeForLock(lockKey);
                    redisTemplate.delete(lockKey);
                    localUid.remove();
                    localInteger.remove();
                }
            }
        } else {
            log.error("lockKey：{} 线程：{} 尝试释放不属于它的锁", lockKey, Thread.currentThread().getName());
        }
    }

    private void scheduleAddTimeForLock(String lockKey, String uuid, long timeout, TimeUnit unit) {
        ScheduledFuture<?> future = taskScheduler.scheduleAtFixedRate(() -> {
            if (uuid.equalsIgnoreCase(redisTemplate.opsForValue().get(lockKey))) {
                // 如果该锁还没有被释放，则将过期时间往后推10s
                redisTemplate.expire(lockKey, 10, unit);
            } else {
                cancelAddTimeForLock(lockKey);
            }
        }, 5000);

        scheduledFuture.set(future);
    }

    private void cancelAddTimeForLock(String lockKey) {
        ScheduledFuture<?> future = scheduledFuture.get();
        if (future != null && !future.isCancelled()) {
            future.cancel(false);
            scheduledFuture.remove();
        }
    }
}
